/**
 * Licensed under the Artistic License; you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://displaytag.sourceforge.net/license.html
 *
 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
package canglang.monitor.core.base.taglib.displaytag.render;

import java.io.IOException;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import canglang.monitor.core.base.taglib.displaytag.exception.ObjectLookupException;
import canglang.monitor.core.base.taglib.displaytag.exception.WrappedRuntimeException;
import canglang.monitor.core.base.taglib.displaytag.model.Column;
import canglang.monitor.core.base.taglib.displaytag.model.HeaderCell;
import canglang.monitor.core.base.taglib.displaytag.model.Row;
import canglang.monitor.core.base.taglib.displaytag.model.TableModel;
import canglang.monitor.core.base.taglib.displaytag.pagination.PaginatedList;
import canglang.monitor.core.base.taglib.displaytag.pagination.SmartListHelper;
import canglang.monitor.core.base.taglib.displaytag.properties.TableProperties;
import canglang.monitor.core.base.taglib.displaytag.util.Anchor;
import canglang.monitor.core.base.taglib.displaytag.util.Href;
import canglang.monitor.core.base.taglib.displaytag.util.HtmlAttributeMap;
import canglang.monitor.core.base.taglib.displaytag.util.PostHref;
import canglang.monitor.core.base.taglib.displaytag.util.TagConstants;
import canglang.monitor.core.base.properties.AppDefaultProperty;


/**
 * A table writer that formats a table in HTML and writes it to a JSP page.
 *
 * @author Fabrizio Giustina
 * @author Jorge L. Barroso                                                                                                   
 * @version $Id$
 * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate
 * @since 1.1
 */
@SuppressWarnings("unchecked")
public class HtmlTableWriter extends TableWriterAdapter {

    /**
     * Logger.
     */
    private static Log log = LogFactory.getLog(HtmlTableWriter.class);

    /**
     * <code>TableModel</code>
     */
    private TableModel tableModel;

    /**
     * <code>TableProperties</code>
     */
    private TableProperties properties;

    /**
     * Output destination.
     */
    private JspWriter out;


    /**
     * base href used for links.
     */
    private Href baseHref;

    /**
     * The paginated list containing the external pagination and sort parameters The presence of this paginated list is
     * what determines if external pagination and sorting is used or not.
     */
    private PaginatedList paginatedList;

    /**
     * Used by various functions when the person wants to do paging.
     */
    private SmartListHelper listHelper;

    private HtmlAttributeMap attributeMap;

    /**
     * Unique table id.
     */
    private String uid;

    private String name;

    private String cPath;

    private String hideTotalRec;

    private String showAnchor;

    /**
     * This table writer uses a <code>TableTag</code> and a <code>JspWriter</code> to do its work.
     *
     * @param out The output destination.
     */
    public HtmlTableWriter(
            TableModel tableModel,
            TableProperties tableProperties,
            Href baseHref,
            JspWriter out,
            PaginatedList paginatedList,
            SmartListHelper listHelper,
            HtmlAttributeMap attributeMap,
            String uid, String name, String cPath, String hideTotalRec, String showAnchor) {
        this.tableModel = tableModel;
        this.properties = tableProperties;
        this.baseHref = baseHref;
        this.out = out;
        this.paginatedList = paginatedList;
        this.listHelper = listHelper;
        this.attributeMap = attributeMap;
        this.uid = uid;
        this.name = name;
        this.cPath = cPath;
        this.hideTotalRec = hideTotalRec;
        this.showAnchor = showAnchor;
    }

    /**
     * Writes a banner containing search result and paging navigation above an HTML table to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTopBanner(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTopBanner(TableModel model) {
        if (this.tableModel.getForm() != null) {
            String js = "<script type=\"text/javascript\">\n"
                    + "function " + this.name + "_displaytagform(formname, fields){\n"
                    + "     var objfrm = document.forms[formname];\n"
                    + "     objfrm.elements['" + this.name + ".exportType'].value='';\n"
                    + "     for (j=fields.length-1;j>=0;j--){\n"
                    + "         var f= objfrm.elements[fields[j].f];\n"
                    + "if (f){f.value=fields[j].v; }\n"
                    + "     }\n"
                    + "     if(window." + this.name + "_paginationCallBack){\n"
                    + "         if(" + this.name + "_paginationCallBack()){\n"
                    + "         if(\"" + this.tableModel.getAction() + "\"!=\"null\"){\n"
                    + "             objfrm.action=\"" + this.tableModel.getAction() + "\";\n"
                    + "         }\n"
                    + "             objfrm.submit();\n"
                    + "         }\n"
                    + "     }else{\n"
                    + "         if(\"" + this.tableModel.getAction() + "\"!=\"null\"){\n"
                    + "             objfrm.action=\"" + this.tableModel.getAction() + "\";\n"
                    + "         }\n"
                    + "         objfrm.submit();\n"
                    + "             }\n"
                    + "}\n\n"

                    + "function " + this.name + "_goTop(formname){\n"
                    + "    var objfrm = document.forms[formname];\n"
                    + "    var totalPage=objfrm.elements['" + this.name + ".totalPageNumber'].value;\n"
                    + "    var toPage = objfrm.elements['" + this.name + ".goTopNumber'].value;\n"
                    + "    if(isToPagelessThanTotalPage(toPage,totalPage)&&toPage>0){\n"
                    + "    " + this.name + "_displaytagform(formname,[{f:'" + this.name + ".pageNumber',v:parseInt(toPage)}]);\n"
                    + "    }else{\n"
                    + " alert(\"\u8f93\u5165\u7684\u9875\u7801\u65e0\u6548\");\n"
                    + "}\n"
                    + "}\n"

                    + "function " + this.name + "_goBottom(formname){\n"
                    + "    var objfrm = document.forms[formname];\n"
                    + "    var totalPage=objfrm.elements['" + this.name + ".totalPageNumber'].value;\n"
                    + "    var toPage = objfrm.elements['" + this.name + ".goBottomNumber'].value;\n"
                    + "    if(isToPagelessThanTotalPage(toPage,totalPage)&&toPage>0){\n"
                    + "    " + this.name + "_displaytagform(formname,[{f:'" + this.name + ".pageNumber',v:parseInt(toPage)}]);\n"
                    + "    }else{\n"
                    + " alert(\"\u8f93\u5165\u7684\u9875\u7801\u65e0\u6548\");\n"
                    + "}\n"
                    + "}\n"

                    + "function " + this.name + "_goTopEnterSubmit(formname){\n"
                    + "     var _key;\n"
                    + "     document.onkeyup = function(e){\n"
                    + "         if (e == null) { \n"        //ie
                    + "             _key = event.keyCode;\n"
                    + "         } else { \n"                // firefox
                    + "             _key = e.which;\n"
                    + "         }\n"
                    + "         if(_key == 13){\n"
                    + this.name + "_goTop(formname);\n"
                    + "         }\n"
                    + "     }\n"
                    + "}\n"

                    + "function " + this.name + "_goBottomEnterSubmit(formname){\n"
                    + "     var _key;\n"
                    + "     document.onkeyup = function(e){\n"
                    + "         if (e == null) { \n"        //ie
                    + "             _key = event.keyCode;\n"
                    + "         } else { \n"                // firefox
                    + "             _key = e.which;\n"
                    + "         }\n"
                    + "         if(_key == 13){\n"
                    + this.name + "_goBottom(formname);\n"
                    + "         }\n"
                    + "     }\n"
                    + "}\n";
            if (showAnchor != null && showAnchor.trim().equalsIgnoreCase("true")) {
                js=js+"document.getElementById(\"currentAnchor\").value=\"#"+this.name+"\";\n";
            }
            js = js + "</script>\n";
            writeFormFields();
            write(js);
        }
        writeSearchResultAndNavigation("TOP", hideTotalRec);
    }

    /**
     * Writes an HTML table's opening tags to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTableOpener(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTableOpener(TableModel model) {
        this.write(getOpenTag());
    }

    private void writeFormFields() {
        writeField(this.name + "." + properties.getPaginationSortParam(), this.paginatedList.getSortCriterion());
        writeField(this.name + "." + properties.getPaginationFullListSizeParam(), this.paginatedList.getFullListSize());
        writeField(this.name + "." + properties.getPaginationSortDirectionParam(), this.paginatedList.getSortDirection());
        writeField(this.name + "." + properties.getPaginationPageNumberParam(), this.paginatedList.getPageNumber());
        writeField(this.name + "." + properties.getPaginationTotalPageNumberParam(), this.paginatedList.getTotalPageNumber());
        writeField(this.name + "." + properties.getPaginationOldPageNumberParam(), this.paginatedList.getOldPageNumber());
        writeField(this.name + "." + properties.getPaginationOldSortDirectionParam(), this.paginatedList.getOldSortDirection());
        writeField(this.name + "." + properties.getPaginationOldSortParam(), this.paginatedList.getOldSortCriterion());
        writeField(this.name + "." + properties.getPaginationPerPageNumberParam(), this.paginatedList.getObjectsPerPage());
        writeField(this.name + "." + properties.getPaginationExportParam(), "");
    }

    /**
     * @param key
     * @param value
     */
    private void writeField(String key, Object value) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<input type=\"hidden\" name=\"");
        buffer.append(esc(key));
        buffer.append("\" value=\"");
        buffer.append(value);
        buffer.append("\"/>");

        write(buffer.toString());
    }

    private String esc(Object value) {
        String valueEscaped;
        valueEscaped = StringUtils.replace(ObjectUtils.toString(value), "\"", "\\\"");
        return valueEscaped;
    }


    /**
     * Writes an HTML table's footer to a JSP page; HTML requires tfoot to appear before tbody.
     */
    protected void writePreBodyFooter(TableModel model) {
        this.write(TagConstants.TAG_TFOOTER_OPEN);
        this.write(model.getFooter());
        this.write(TagConstants.TAG_TFOOTER_CLOSE);
    }

    /**
     * Writes the start of an HTML table's body to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTableBodyOpener(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTableBodyOpener(TableModel model) {
        this.write(TagConstants.TAG_TBODY_OPEN);

    }

    /**
     * Writes the end of an HTML table's body to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTableBodyCloser(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTableBodyCloser(TableModel model) {
        this.write(TagConstants.TAG_TBODY_CLOSE);
    }

    /**
     * Writes the closing structure of an HTML table to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTableCloser(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTableCloser(TableModel model) {
        this.write(TagConstants.TAG_OPENCLOSING);
        this.write(TagConstants.TABLE_TAG_NAME);
        this.write(TagConstants.TAG_CLOSE);
    }

    /**
     * Writes a banner containing search result, paging navigation, and export links below an HTML table to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeBottomBanner(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeBottomBanner(TableModel model) {
        writeNavigationAndExportLinks();
    }


    /**
     * Writes an HTML table's row-opening tag to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeRowOpener(canglang.monitor.core.base.taglib.displaytag.model.Row)
     */
    protected void writeRowOpener(Row row) {
        this.write(row.getOpenTag());
    }

    /**
     * Writes an HTML table's column-opening tag to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeColumnOpener(canglang.monitor.core.base.taglib.displaytag.model.Column)
     */
    protected void writeColumnOpener(Column column) throws ObjectLookupException {
        this.write(column.getOpenTag());
    }

    /**
     * Writes an HTML table's column-closing tag to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeColumnCloser(canglang.monitor.core.base.taglib.displaytag.model.Column)
     */
    protected void writeColumnCloser(Column column) {
        this.write(column.getCloseTag());
    }

    /**
     * Writes to a JSP page an HTML table row that has no columns.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeRowWithNoColumns(java.lang.String)
     */
    protected void writeRowWithNoColumns(String rowValue) {
        this.write(TagConstants.TAG_TD_OPEN);
        this.write(rowValue);
        this.write(TagConstants.TAG_TD_CLOSE);
    }

    /**
     * Writes an HTML table's row-closing tag to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeRowCloser(canglang.monitor.core.base.taglib.displaytag.model.Row)
     */
    protected void writeRowCloser(Row row) {
        this.write(row.getCloseTag());
    }

    /**
     * Writes an HTML message to a JSP page explaining that the table model contains no data.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeEmptyListMessage(java.lang.String)
     */
    protected void writeEmptyListMessage(String emptyListMessage) {
        this.write(emptyListMessage);
    }

    /**
     * Writes a HTML table column value to a JSP page.
     */
    protected void writeColumnValue(Object value, Column column) {
        this.write(value);
    }

    /**
     * Writes an HTML message to a JSP page explaining that the row contains no data.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeEmptyListRowMessage(java.lang.String)
     */
    protected void writeEmptyListRowMessage(String message) {
        this.write(message);
    }

    /**
     * Writes an HTML table's column header to a JSP page.
     *
     * @see canglang.monitor.core.base.taglib.displaytag.render.TableWriterTemplate#writeTableHeader(canglang.monitor.core.base.taglib.displaytag.model.TableModel)
     */
    protected void writeTableHeader(TableModel model) {

        if (log.isDebugEnabled()) {
            log.debug("[" + tableModel.getId() + "] getTableHeader called");
        }

        // open thead
        write(TagConstants.TAG_THEAD_OPEN);

        // open tr
        write(TagConstants.TAG_TR_OPEN);

        // no columns?
        if (this.tableModel.isEmpty()) {
            write(TagConstants.TAG_TH_OPEN);
            write(TagConstants.TAG_TH_CLOSE);
        }

        // iterator on columns for header

        for (HeaderCell headerCell1 : this.tableModel.getHeaderCellList()) {
            // get the header cell
            HeaderCell headerCell = headerCell1;

            if (headerCell.getSortable()) {
                String cssSortable = this.properties.getCssSortable();
                headerCell.addHeaderClass(cssSortable);
            }

            // if sorted add styles
            if (headerCell.isAlreadySorted()) {
                // sorted css class
                headerCell.addHeaderClass(this.properties.getCssSorted());

                // sort order css class
                headerCell.addHeaderClass(this.properties.getCssOrder(this.tableModel.isSortOrderAscending()));
            }

            // append th with html attributes
            write(headerCell.getHeaderOpenTag());

            // title
            String header = headerCell.getTitle();

            // column is sortable, create link
            if (headerCell.getSortable()) {
                // creates the link for sorting
                Anchor anchor = new Anchor(getSortingHref(headerCell), header);

                // append to buffer
                header = anchor.toString();
//                if(headerCell.getHtmlAttributes()!=null){
//                    if(headerCell.getHtmlAttributes().toString().equalsIgnoreCase("sortable sorted order1")){
//                        header = anchor.toString()+"<img src='document.getElementsById[currentContextPath]/images/arrow_up.gif'>";
//                    }else if(headerCell.getHtmlAttributes().toString().equalsIgnoreCase("sortable sorted order2")){
//                        header = anchor.toString()+"<img src='document.getElementsById[currentContextPath]/images/arrow_down.gif'>";
//                    }
//                }
            }
            write(header);
            write(headerCell.getHeaderCloseTag());
        }

        // close tr
        write(TagConstants.TAG_TR_CLOSE);

        // close thead
        write(TagConstants.TAG_THEAD_CLOSE);

        if (log.isDebugEnabled()) {
            log.debug("[" + tableModel.getId() + "] getTableHeader end");
        }
    }

    /**
     * Generates the link to be added to a column header for sorting.
     *
     * @param headerCell header cell the link should be added to
     * @return Href for sorting
     */
    private Href getSortingHref(HeaderCell headerCell) {
        // costruct Href from base href, preserving parameters
        Href href = (Href) this.baseHref.clone();

        if (this.tableModel.getForm() != null) {
            href = new PostHref(href, tableModel.getForm(), name);
        }

        if (properties.getPaginationSkipPageNumberInSort()) {
            href.removeParameter(properties.getPaginationPageNumberParam());
        }

        String sortProperty = headerCell.getSortProperty();
        if (sortProperty == null) {
            sortProperty = headerCell.getBeanPropertyName();
        }

        href.addParameter(this.name + "." + properties.getPaginationSortParam(), sortProperty);
        String dirParam;
        if (headerCell.isAlreadySorted()) {
            dirParam = tableModel.isSortOrderAscending() ? properties.getPaginationDescValue() : properties
                    .getPaginationAscValue();
        } else {
            dirParam = properties.getPaginationAscValue();
        }
        href.addParameter(this.name + "." + properties.getPaginationSortDirectionParam(), dirParam);

        return href;
    }


    /**
     * Generates table footer with links for export commands.
     */
    public void writeNavigationAndExportLinks() {
        // Put the page stuff there if it needs to be there...
        if (this.properties.getAddPagingBannerBottom()) {
            writeSearchResultAndNavigation("BOTTOM", hideTotalRec);
        }
    }

    /**
     * generates the search result and navigation bar.
     */
    public void writeSearchResultAndNavigation(String position, String hideTotalRec) {
        write("<div>");
        if (showAnchor != null && showAnchor.trim().equalsIgnoreCase("true")) {
            write("<a name=\"" + this.name + "\"/>");
            showAnchor="";
        }
        write("<table width='100%' class='sectionTop'><tr><td width='50%' class='paginationFont' align='left'>");

        Href navigationHref = new PostHref((Href) this.baseHref.clone(), tableModel.getForm(), name);
        if (hideTotalRec == null || !hideTotalRec.trim().equalsIgnoreCase("true")) {
            write(this.listHelper.getSearchResultsSummary());
        }
        write("</td><td width='50%' class='paginationFont' align='right'>");
        String pageParameter;

        pageParameter = this.name + "." + properties.getPaginationPageNumberParam();

        write(this.listHelper.getPageNavigationBar(navigationHref, pageParameter, cPath));
        write("&nbsp;&nbsp;&nbsp;\u7b2c");
        if ("TOP".equalsIgnoreCase(position)) {
            String query= AppDefaultProperty.getProperty("label.pagination.submit");
            write("<input type='text'  class='txtFill' name='" + this.name + ".goTopNumber' maxlength='4' size='4' onkeyup=\"" + this.name + "_goTopEnterSubmit('" + this.tableModel.getForm() + "')\"" + " value='" + this.paginatedList.getPageNumber() + "'> \u9875\uff0c\u5171 " + (this.paginatedList.getTotalPageNumber()==0?1:this.paginatedList.getTotalPageNumber())+"\u9875");
            write("&nbsp;&nbsp;&nbsp;");
            write("<input name='goButton' type='button' class='buttonGO' value="+query+" onclick=\"javascript:" + this.name + "_goTop('" + this.tableModel.getForm() + "')\">");
        } else if ("BOTTOM".equalsIgnoreCase(position)) {
            String query= AppDefaultProperty.getProperty("label.pagination.submit");
            write("<input type='text'  class='txtFill' name='" + this.name + ".goBottomNumber' maxlength='4' size='4' onkeyup=\"" + this.name + "_goBottomEnterSubmit('" + this.tableModel.getForm() + "')\"" + " value='" + this.paginatedList.getPageNumber() + "'> \u9875\uff0c\u5171 " + (this.paginatedList.getTotalPageNumber()==0?1:this.paginatedList.getTotalPageNumber())+"\u9875");
            write("&nbsp;&nbsp;&nbsp;");
            write("<input name='goButton' type='button' class='buttonGO' value="+query+" onclick=\"javascript:" + this.name + "_goBottom('" + this.tableModel.getForm() + "')\">");
        }
        write("</td></tr></table></div>");
    }


    /**
     * create the open tag containing all the attributes.
     *
     * @return open tag string: <code>%lt;table attribute="value" ... ></code>
     */
    public String getOpenTag() {

        if (this.uid != null && attributeMap.get(TagConstants.ATTRIBUTE_ID) == null) {
            // we need to clone the attribute map in order to "fix" the html id when using only the "uid" attribute
            Map<String, String> localAttributeMap = (Map<String, String>) attributeMap.clone();
            localAttributeMap.put(TagConstants.ATTRIBUTE_ID, this.uid);

            StringBuffer buffer = new StringBuffer();
            buffer.append(TagConstants.TAG_OPEN).append(TagConstants.TABLE_TAG_NAME);
            buffer.append(localAttributeMap);
            buffer.append(TagConstants.TAG_CLOSE);

            return buffer.toString();

        }

        // fast, no clone
        StringBuffer buffer = new StringBuffer();

        buffer.append(TagConstants.TAG_OPEN).append(TagConstants.TABLE_TAG_NAME);
        buffer.append(attributeMap);
        buffer.append(TagConstants.TAG_CLOSE);

        return buffer.toString();
    }

    /**
     * Utility method.
     *
     * @param string String
     */
    public void write(String string) {
        if (string != null) {
            try {
                out.write(string);
            }
            catch (IOException e) {
                throw new WrappedRuntimeException(getClass(), e);
            }
        }

    }

    public void writeTable(TableModel model, String id) throws JspException {
        super.writeTable(model, id);
    }


    /**
     * Utility method.
     *
     * @param string String
     */
    public void write(Object string) {
        if (string != null) {
            try {
                out.write(string.toString());
            }
            catch (IOException e) {
                throw new WrappedRuntimeException(getClass(), e);
            }
        }

    }


}
